﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CommonTypes;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels;
using System.Collections;
using System.Threading;

namespace ClientApp
{
    class Program
    {
        public static IServer serverReference;
        public static IServersManager serversManager;
        private static TcpChannel channel;
        //hash table que conterá as threads cuja missão é abordar cada convidado para que marquem um slot como "acknowledged".
        //a chave da 1ª hash será o numero da reserva e a chave da 2ª será o nome do convidado com o qual a thread irá comunicar
        private static Dictionary<int, Dictionary<String, Thread>> threadsToReservate = new Dictionary<int, Dictionary<String, Thread>>();
        private static Dictionary<int, Dictionary<String, Thread>> threadsToBook = new Dictionary<int, Dictionary<String, Thread>>();
        private static Dictionary<int, Dictionary<String, Thread>> threadsToCommit = new Dictionary<int, Dictionary<String, Thread>>();
        private static Dictionary<int, Dictionary<String, Thread>> threadsToSayAbort = new Dictionary<int, Dictionary<String, Thread>>();

        /***************************************************************************/
        /*********************************MAIN**************************************/
        /***************************************************************************/

        static void Main(string[] args)
        {
            Console.WriteLine(args[1]);
            String[] ipPorts = args[1].Split(new Char[] { ':' });
            String serverAddress = args[2];


            //o cliente cria e regista (disponibiliza) um canal
            channel = new TcpChannel(Int32.Parse(ipPorts[1]));
            ChannelServices.RegisterChannel(channel, true);
            RemotingConfiguration.RegisterWellKnownServiceType(typeof(Client), "client", WellKnownObjectMode.Singleton);

            //Obtem endereço do servidor primário

            serverReference = (IServer)Activator.GetObject(typeof(IServer), "tcp://" + serverAddress + "/Server");
            serverReference.connect(args[0], args[1]);

            Console.WriteLine(serverAddress);
            Console.ReadLine();
        }


        /***************************************************************************/
        /*************************CLIENT*******************************/
        /***************************************************************************/

        public class Client : MarshalByRefObject, IClient
        {
            //cada cliente mantém informação referente ao seu calendário (papel de convidado)
            private Dictionary<int, SlotCal> calendar = new Dictionary<int, SlotCal>();

            //cada cliente mantém a "lista" de reservas das quais é anfitrião (papel de anfitrião)
            private Dictionary<int, Reservation> reservationList = new Dictionary<int, Reservation>();

            //variável que guarda info sobre o último endereço do cliente
            private String lastIpPort;



            /**************************************connect*************************************/
            public void connect(String username, String ipPort)
            {

                //caso o novo endereço seja diferente do anterior
                if (ipPort != lastIpPort)
                {
                    //faz-se split porque só precisamos de passar o porto, o IP não!
                    String[] ipPorts = ipPort.Split(new Char[] { ':' });

                    //o cliente cria e regista (disponibiliza) um canal
                    channel.StopListening(new object());
                    ChannelServices.UnregisterChannel(channel);

                    channel = new TcpChannel(Int32.Parse(ipPorts[1]));
                    ChannelServices.RegisterChannel(channel, true);
                    //RemotingConfiguration.RegisterWellKnownServiceType(typeof(Client), "client", WellKnownObjectMode.Singleton);
                }

                String res = Program.serverReference.connect(username, ipPort);

                //"ressuscitamos" as threads referentes a cada reserva do cliente em causa
                resumeTreads(threadsToReservate);
                resumeTreads(threadsToBook);
                resumeTreads(threadsToCommit);
                resumeTreads(threadsToSayAbort);

                Console.WriteLine(username + ": Conectei");
            }



            /**************************************disconnect*************************************/

            public void disconnect(String username)
            {
                //antes de desconectar guardamos o último ipPort utilizado
                lastIpPort = Program.serverReference.getClientRef(username);

                //suspendemos todas as threads para reservar, book e commit referentes a reservas do cliente em causa
                suspendThreads(threadsToReservate);
                suspendThreads(threadsToBook);
                suspendThreads(threadsToCommit);
                suspendThreads(threadsToSayAbort);


                String res = Program.serverReference.disconnect(username);
                Console.WriteLine(username + ": Desconectei ");
            }



            /**************************************resumeTreads*************************************/

            public void resumeTreads(Dictionary<int, Dictionary<String, Thread>> threadsToResume)
            {
                foreach (int reservationId in threadsToResume.Keys)
                    foreach (Thread t in threadsToResume[reservationId].Values)
                    {
                        //se não forem threads que já haviam completado o seu trabalho
                        if (t.ThreadState.CompareTo(ThreadState.Suspended) == 0)
                            t.Resume();
                    }
            }



            /**************************************suspendThreads*************************************/

            public void suspendThreads(Dictionary<int, Dictionary<String, Thread>> threadsToSuspend)
            {
                foreach (int reservationId in threadsToSuspend.Keys)
                    foreach (Thread t in threadsToSuspend[reservationId].Values)
                    {
                        //se não forem threads que já haviam completado o seu trabalho
                        if (t.ThreadState.CompareTo(ThreadState.Stopped) != 0)
                            t.Suspend();
                    }
            }


            /**************************************readCalendar*************************************/
            public SlotsToPuppet[] readCalendar()
            {
                List<SlotsToPuppet> slotsListToPuppet = new List<SlotsToPuppet>();

                //garantir exclusão mútua no acesso ao calendário, pois é partilhado por várias threads
                lock (calendar)
                {
                    //listamos os ID dos slots presentes no calendario
                    List<int> calendarSlotNumbers = calendar.Keys.ToList();

                    //para cada slot assinalado no calendario do convidado, adicionamos uma "representação" sua
                    //à lista que será enviada ao puppet master
                    foreach (int slotNumber in calendarSlotNumbers)
                    {
                        slotsListToPuppet.Add(new SlotsToPuppet(slotNumber, calendar[slotNumber].State, calendar[slotNumber].ChosenReservation));
                    }
                }
                return slotsListToPuppet.ToArray();
            }



            /***************************************************************************/
            /*************************HOST (ANFITRIÃO)*******************************/
            /***************************************************************************/



            /**************************************hostReservation*************************************/

            //procedimento que permite lançar uma thread para cada reserva pretendida
            public void hostReservation(String description, List<String> guestList, List<int> slotList)
            {
                Thread newThread = new Thread(new ParameterizedThreadStart(this.eachHostReservation));
                newThread.Start(new object[] { description, guestList, slotList });
            }



            /**************************************eachHostReservation*************************************/

            //procedimento executado para cada reserva pretendida
            public void eachHostReservation(object data)
            {
                object[] userData = (object[])data;

                //descrição da reserva em causa
                String description = (String)userData[0];
                //lista com os nomes dos convidados
                List<String> guestList = (List<String>)userData[1];
                //lista com os slots desejados pelo anfitrião
                List<int> slotList = (List<int>)userData[2];
                //terá o ID (ticket) atribuido pelo servidor
                int reservationId;
                //terá o objecto referente a reserva que será criada
                Reservation reservation;
                int slotToBook;


                //contacta o servidor de modo a obter o ticket (ID) da reserva
                reservationId = Program.serverReference.grantTicket();

                //criamos o objecto referente a reserva criada
                reservation = new Reservation(reservationId, description, guestList, slotList);

                lock (reservationList)
                {
                    //inserimos essa reserva na lista de reservas deste anfitrião
                    reservationList.Add(reservationId, reservation);
                }

                Console.WriteLine("foi criada uma thread para a reserva " + reservationId);
                Console.WriteLine("esta vai criar threads para reservar cada cliente");
                //colocamos na hash uma entrada referente a esta reserva
                threadsToSayAbort.Add(reservationId, new Dictionary<string, Thread>());

                while (true)
                {
                    lock (threadsToReservate)
                    {
                        threadsToReservate.Add(reservationId, new Dictionary<string, Thread>());

                    }

                    //iremos criar uma thread que trata da reserva de cada client
                    foreach (String guestUsername in guestList)
                    {
                        Thread newThread = new Thread(new ParameterizedThreadStart(this.tolkToGuestToReservate));

                        //adicionamos essa thread à lista de threads para reservar
                        threadsToReservate[reservationId].Add(guestUsername, newThread);
                        newThread.Start(new object[] { reservationId, guestUsername, description, slotList });
                    }


                    //acompanhamos o processo de reserva de slots nos convidados.
                    //Só saímos daqui se a reserva for abortada ou se for possível passar para "book"
                    monitoringReservations(reservationId, guestList);

                    lock (reservationList)
                    {
                        //depois das reservas obtemos o slot consensual preferido para fazer book
                        slotToBook = reservationList[reservationId].ConsensualSlots.First();
                        Console.WriteLine("reserva " + reservationId + ". O slot para fazer book é: " + slotToBook);
                        //removemos o slotToBook da lista de consensuais, actualizando essa lista para outras tentativas
                        reservationList[reservationId].ConsensualSlots.Remove(slotToBook);
                        slotList = reservationList[reservationId].ConsensualSlots;
                    }
                    Console.WriteLine("reserva" + reservationId + " vai criar threads para fazer book de cada cliente");
                    lock (threadsToBook)
                    {
                        threadsToBook.Add(reservationId, new Dictionary<string, Thread>());

                        //iremos criar uma thread que trata do book de cada client
                        foreach (String guestUsername in guestList)
                        {
                            Thread newThread = new Thread(new ParameterizedThreadStart(this.tolkToGuestToBook));
                            //adicionamos essa thread à lista de threads para fazer "book"

                            threadsToBook[reservationId].Add(guestUsername, newThread);
                            newThread.Start(new object[] { reservationId, guestUsername, slotToBook });
                        }
                    }

                    //acompanhamos o processo de book de slots nos convidados.
                    //Só saímos daqui se a reserva for abortada (result=0) ou se for possível passar para "commit" (result=1)
                    int result = monitoringBooks(reservationId, guestList);

                    if (result == 1)
                        break;
                }

                Console.WriteLine("reserva" + reservationId + " vai criar threads para fazer commit de cada cliente");
                lock (threadsToCommit)
                {
                    threadsToCommit.Add(reservationId, new Dictionary<string, Thread>());

                    //iremos criar uma thread que trata do commit de cada client
                    foreach (String guestUsername in guestList)
                    {
                        Thread newThread = new Thread(new ParameterizedThreadStart(this.tolkToGuestToCommit));
                        threadsToCommit[reservationId].Add(guestUsername, newThread);
                        newThread.Start(new object[] { reservationId, guestUsername, slotToBook });
                    }
                }
            }



            /**************************************tolkToGuestToReservate*************************************/

            //procedimento, executado por cada thread, que permite fazer a reserva para um dado cliente 
            public void tolkToGuestToReservate(object data)
            {
                Object[] userData = (Object[])data;
                int reservationId = (int)userData[0];
                String guestUsername = (String)userData[1];
                String description = (String)userData[2];
                List<int> slotList = (List<int>)userData[3];

                Console.WriteLine("reserva " + reservationId);
                Console.WriteLine("cheguei ao tolkToGuestToReservate para falar com " + guestUsername);
                //lista de todos os slots para os quais o convidado está disponível
                List<int> availableSlots = new List<int>();

                IClient client = getGuestReference(guestUsername);

                //convidamos para a reservation
                availableSlots = client.reservation(reservationId, guestUsername, description, slotList);
                Console.WriteLine("reserva " + reservationId + ": convidado " + guestUsername + " esta disponivel para os slotes:");
                foreach (int s in availableSlots)
                    Console.WriteLine(s);

                lock (reservationList)
                {
                    //actualizamos a lista de slots consensuais, de acordo com a disponibilidade de cada convidado
                    reservationList[reservationId].updateConsensualSlots(availableSlots);
                    //assinalamos que recebemos uma resposta do convidado em causa
                    reservationList[reservationId].GuestList[guestUsername].Slot.State = "acknowledged";
                }
                return;
            }



            /**************************************monitoringReservations*************************************/

            //procedimento que acompanha o processo de reserva de slots. Só termina em caso de abort ou quando for possível passar para "book"
            public void monitoringReservations(int reservationId, List<String> guestList)
            {
                //deste modo esperamos no máximo durante 10s por um convidado
                for (int i = 0; i < 3; i++)
                {
                    //vemos se alguum convidado mostrou INdisponibilidade para todos os slots propostos
                    //ou seja, se já nao existem slots consensuais. Se sim, aborta-se a reserva

                    //caso se venha a confirmar...
                    if (reservationList[reservationId].ConsensualSlots.Count == 0)
                    {
                        Console.WriteLine("nao ha slots consensuais. reserva será abortada");
                        hostAbortReservation(reservationId, guestList, threadsToReservate);
                        //terminamos a thread que tentava fazer esta reservation
                        Thread.CurrentThread.Abort();
                    }
                    //havendo slots consensuais, até então verificamos se todos já responderam
                    //número de convidados que já responderam ao pedido de reserva
                    int guestsWhoResponded = 0;

                    lock (reservationList)
                    {
                        foreach (String guestUsername in guestList)
                        {
                            //vemos se todos os convidados já responderam
                            if (reservationList[reservationId].GuestList[guestUsername].Slot.State == "acknowledged")
                            {
                                //a cada convidado que já tenha respondido incrementamos o numero
                                guestsWhoResponded++;
                            }
                            else
                                //se algum ainda não tenha respondido saimos do "for"
                                break;
                        }
                    }

                    //no fim verificamos se todos já responderam afirmativamente
                    if (guestsWhoResponded == guestList.Count)
                        return;
                    //se nem todos responderam, aguardamos algum tempo e voltamos a verificar
                    Thread.Sleep(4000);
                }
                //só sai do ciclo quando algum convidado não responder em 12s
                Console.WriteLine("reserva " + reservationId + " sera abortada, algum convidado demorou muito para conectar-se.");
                hostAbortReservation(reservationId, guestList, threadsToReservate);
                Thread.CurrentThread.Abort();
            }



            /**************************************tolkToGuestToBook*************************************/

            //procedimento, executado por cada thread, que permite fazer book para um dado cliente 
            public void tolkToGuestToBook(object data)
            {
                Object[] userData = (Object[])data;
                int reservationId = (int)userData[0];
                String guestUsername = (String)userData[1];
                int slotToBook = (int)userData[2];

                Console.WriteLine("reserva " + reservationId);
                Console.WriteLine("cheguei ao tolkToGuestToBook para falar com " + guestUsername);
                IClient client = getGuestReference(guestUsername);

                //pede para o cliente fazer book de um determinado slot
                //se o book for efectuado com sucesso devolve o slot, c.c. devolve 0
                int result = client.book(reservationId, guestUsername, slotToBook);
                lock (reservationList)
                {
                    if (result == slotToBook)
                    {
                        Console.WriteLine("reserva " + reservationId + ": convidado " + guestUsername + " fez book do slot " + result);
                        reservationList[reservationId].GuestList[guestUsername].Slot.State = "booked";
                    }
                    else
                    {
                        Console.WriteLine("reserva " + reservationId + ": convidado " + guestUsername + " NÃO fez book do slot " + result);
                        reservationList[reservationId].GuestList[guestUsername].Slot.State = "aborted";
                    }
                }
            }



            /**************************************monitoringBooks*************************************/

            //procedimento que acompanha o processo de book de slots. Só termina em caso de abort ou quando for possível passar para "commit"
            public int monitoringBooks(int reservationId, List<String> guestList)
            {
                while (true)
                {
                    //número de convidados que já responderam ao pedido de book
                    int guestsWhoResponded = 0;

                    //vemos se alguum convidado mostrou INdisponibilidade, escolhendo outra reserva 
                    //ou seja, temos que abortar esta reserva
                    lock (reservationList)
                    {
                        foreach (String guestUsername in guestList)
                        {
                            //vemos se todos os convidados já responderam
                            if (reservationList[reservationId].GuestList[guestUsername].Slot.State == "booked")
                                //a cada convidado que já tenha respondido incrementamos o numero
                                guestsWhoResponded++;
                            else
                                //se algum convidado abortou esta reserva, todos devem fazer o mesmo
                                if (reservationList[reservationId].GuestList[guestUsername].Slot.State == "aborted")
                                {
                                    Console.WriteLine("reserva "+reservationId+" convidado " + guestUsername + " no book, pediu para abortar");
                                    hostAbortReservation(reservationId, guestList, threadsToReservate);
                                    hostAbortReservation(reservationId, guestList, threadsToBook);
                                    return 0;
                                }
                                else
                                    //se algum ainda não tenha respondido saimos do "for"
                                    break;
                        }
                    }
                    //no fim verificamos se todos já responderam afirmativamente
                    if (guestsWhoResponded == guestList.Count)
                        return 1;
                    //se nem todos responderam, aguardamos algum tempo e voltamos a verificar
                    Thread.Sleep(4000);
                }
            }



            /**************************************tolkToGuestToCommit*************************************/

            //procedimento, executado por cada thread, que permite fazer commit para um dado cliente 
            public void tolkToGuestToCommit(object data)
            {
                Object[] userData = (Object[])data;
                int reservationId = (int)userData[0];
                String guestUsername = (String)userData[1];
                int slotToCommit = (int)userData[2];

                Console.WriteLine("reserva " + reservationId);
                Console.WriteLine("cheguei ao tolkToGuestToCommit para falar com " + guestUsername);

                IClient client = getGuestReference(guestUsername);

                bool result = client.commit(reservationId, guestUsername, slotToCommit);
                if (result == true)
                {
                    Console.WriteLine("reserva " + reservationId + ": convidado " + guestUsername + " fez commit do slot " + result);
                    lock (reservationList)
                    {
                        reservationList[reservationId].GuestList[guestUsername].Slot.State = "commited";
                    }
                }
                else
                    Thread.Sleep(4000);
            }



            /************************************connectToGuest***************************************/

            //procedimento que permite obter a referencia remota de um dado cliente
            IClient getGuestReference(String guestUsername)
            {
                //este ciclo existe porque se não for possível contactar o convidado, o anfitrião aguarda um certo tempo e volta a tentar
                while (true)
                {
                    try
                    {
                        //pedimos o IpPort do convidado ao servidor
                        String guestIpPort = Program.serverReference.getClientRef(guestUsername);
                        //obtemos a referencia do cliente em questão

                        IClient client = (IClient)Activator.GetObject(typeof(IClient), "tcp://" + guestIpPort + "/client");
                        return client;
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine("nao deu para ligar");
                        //anfitriao aguarda por 4s
                        Thread.Sleep(4000);
                        continue;
                    }
                }
            }



            /************************************hostAbortReservation***************************************/

            //metodo que aborta as threads do anfitrião para uma dada reserva e avisa os convidados da mesma
            public void hostAbortReservation(int reservationId, List<String> guestList, Dictionary<int, Dictionary<String, Thread>> threadsToAbort)
            {
                //abortamos todas as thread que estao a tratar desta reserva com cada convidado (lado anfitrião)
                foreach (Thread t in threadsToAbort[reservationId].Values)
                {
                    if(t.ThreadState.CompareTo(ThreadState.Suspended)!=0)
                    t.Abort();
                }

                //para cada convidado envolvido nesta reserva, lançamos uma thread que o contacta e avisa sobre o aborto da reserva
                foreach (String guestUsername in guestList)
                {
                    //removemos as threads dedicadas a cada convidado
                    threadsToAbort[reservationId].Remove(guestUsername);

                    Thread newThread = new Thread(new ParameterizedThreadStart(this.sayGuestToAbort));
                    // threadsToSayAbort[reservationId].Add(guestUsername, newThread);
                    newThread.Start(new object[] { reservationId, guestUsername });
                }
                //removemos a entrada da hash para as threads dedicadas a cada cliente
                threadsToAbort.Remove(reservationId);
            }



            /************************************sayGuestToAbort***************************************/

            //procedimento que avisa um dado cliente sobre o aborto de uma dada reserva
            public void sayGuestToAbort(Object data)
            {
                Object[] userData = (Object[])data;
                int reservationId = (int)userData[0];
                String guestUsername = (String)userData[1];

                IClient client = getGuestReference(guestUsername);
                client.guestAbortReservation(reservationId);
            }



            /***************************************************************************/
            /*************************GUEST (CONVIDADO)*******************************/
            /***************************************************************************/



            /************************************reservation***************************************/

            //procedimento que permite marcar um dado slot do calendário como "acknowledge"
            public List<int> reservation(int reservationId, String gestUsername, String description, List<int> desiredSlotList)
            {
                //se nao existir é porque ainda não foi proposta qq reserva para este slot
                Boolean slotExist = false;
                SlotCal slotCal = new SlotCal();

                //lista de todos os slots para os quais o convidado está disponível
                List<int> availableSlots = new List<int>();

                //ver disponibilidade para cada slot
                foreach (int slotNumber in desiredSlotList)
                {
                    lock (calendar)
                    {
                        slotExist = calendar.ContainsKey(slotNumber);

                        //caso o slot não tenha tido qq proposta antes...
                        if (!slotExist)
                        {
                            //criamos e adicionamos ao calendário do convidado um slotCal
                            slotCal = new SlotCal(slotNumber, "acknowledged", reservationId, new ReservationState(reservationId, false));
                            calendar.Add(slotNumber, slotCal);
                            availableSlots.Add(slotNumber);
                        }
                        //caso o slot já exista no calendário, é sinal que já teve outras propostas
                        else
                        {
                            //para registar a reservation, o slot nao pode estar a BOOKED nem a ASSIGNED
                            slotCal = calendar[slotNumber];
                            if (!slotCal.State.Equals("booked") && !slotCal.State.Equals("assigned"))
                            {
                                //adicionamos mais esta reservation as que concorrem por este slot
                                slotCal.Reservations.Add(reservationId, new ReservationState(reservationId, false));
                                availableSlots.Add(slotNumber);
                            }
                        }
                    }
                }
                return availableSlots;
            }



            /************************************book***************************************/

            //procedimento que permite marcar um dado slot do calendário como "booked"
            public int book(int reservationId, String guestUsername, int slotToBook)
            {
                //antes de mais, "libertamos" os outros slots que foram apontados por esta reserva
                //ou seja, esta reserva deixará de influenciar os seus estados
                freeSlots(reservationId, slotToBook);

                //assinalamos que esta reserva pediu para fazer "book"
                calendar[slotToBook].Reservations[reservationId].Booked = true;

                //caso nenhuma outra reserva tenha tentado marcar este slot como "booked"
                if (calendar[slotToBook].State.Equals("acknowledged"))
                {
                    //marcamos o slot como "booked" (pelo menos um anfitrião pediu para fazer book)
                    calendar[slotToBook].State = "booked";
                }
                while (true)
                {
                //se a reserva mais antiga (menor ID) é que pediu "book"
                if (oldestReservation(reservationId, slotToBook))
                {
                    //indicamos qual foi a reserva escolhida, para que as outras saibam
                    calendar[slotToBook].ChosenReservation = reservationId;
                    return slotToBook;
                }
                //caso contrário a resposta só é dada quando houver uma decisão
                else
                {
                    
                        //dá um tempo para que haja uma escolha sobre que reserva fazer book
                        Thread.Sleep(4000);
                        //verifica se já houve uma escolha. Se ainda nao volta a esperar
                        if (calendar[slotToBook].ChosenReservation == 0)
                            continue;
                        else
                        {
                            //se a escolha recaiu sobre esta reserva...
                            if (calendar[slotToBook].ChosenReservation == reservationId)
                                return slotToBook;
                            return 0;
                        }
                    }
                }
            }



            /************************************commit***************************************/

            //procedimento que permite, finalmente, marcar uma dado slot como assigned
            public bool commit(int reservationId, String guestUsername, int slotToCommit)
            {
                calendar[slotToCommit].State = "assigned";
                return true;
            }



            /************************************guestAbortReservation***************************************/

            //procedimento que elimina qualquer influencia, de uma reserva abortada, sobre os slots do calendário
            public void guestAbortReservation(int reservationId)
            {
                //indicamos o "slotToBook" como 0, porquen não existe nenhum slot 0.
                //deste modo são libertados todos os slots referentes a esta reserva
                freeSlots(reservationId, 0);
            }



            /************************************freeSlots***************************************/

            //procedimento que elimina qualquer influencia de uma dada reserva sobre todos os slots que não forma escolhidos para book
            public void freeSlots(int reservationId, int slotToBook)
            {
                //terá a lista de todos os slots cujo estado é influenciado por esta reserva
                //, porém não foram escolhidos para book
                List<int> slotsToFree = new List<int>();

                lock (calendar)
                {
                    foreach (SlotCal slot in calendar.Values)
                    {
                        //só não tocamos no slot que foi escolhido para fazer book
                        if (slot.Number != slotToBook)
                        {
                            if (calendar[slot.Number].Reservations.ContainsKey(reservationId))
                                slotsToFree.Add(slot.Number);
                        }
                    }

                    foreach (int slotNumber in slotsToFree)
                    {
                        //remove a info existente no slot, referente a essa reserva
                        calendar[slotNumber].Reservations.Remove(reservationId);
                        //se era a única reserva interessada nesse slot, removemos o slot do calendario
                        if (calendar[slotNumber].Reservations.Count == 0)
                            calendar.Remove(slotNumber);
                    }
                }
            }



            /************************************oldestReservation***************************************/

            //procedimento que permite ver se uma dada reserva é a mais antiga das que concorrem por um dado slot (se tem o menor ID)
            bool oldestReservation(int reservationId, int slotToBook)
            {
                //se a reserva que pediu "book" for a menor entre as interessada neste slot...
                if (calendar[slotToBook].Reservations.Keys.Min() == reservationId)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }



            public override object InitializeLifetimeService()
            {
                return null;
            }

            public String LastIpPort
            {
                get { return lastIpPort; }
                set { lastIpPort = value; }
            }

        }



        /***************************************************************************/
        /**********************ESTRUTURAS DE DADOS PARA HOST************************/
        /***************************************************************************/



        /********************************Reservation********************************/

        public class Reservation
        {

            private int id;

            private String description;

            //lista dos convidados da reserva
            private Dictionary<String, Guest> guestList = new Dictionary<String, Guest>();

            //lista de inteiros que representam os slots desejados pelo anfitrião (por ordem de preferência)
            private List<int> desiredSlots;

            //lista de inteiros que representam os slots desejados pelo anfitrião
            private List<int> consensualSlots;

            //slot sobre o qual estamos a tentar fazer a reserva
            private int currentSlot;

            public Reservation() { }

            public Reservation(int id, String description, List<String> guestList, List<int> slotList)
            {
                this.id = id;
                this.description = description;
                this.desiredSlots = slotList;
                //inicialmente assumimos que todos os slots são consensuais
                //iremos remove-los a medida que as respostas dos convidados nao os contemplarem
                this.consensualSlots = slotList;

                //para cada username vamos construir um objecto do tipo Guest e colocamo-lo na lista de convidados
                foreach (String guestUsername in guestList)
                {
                    Guest guest = new Guest();
                    //atribuimos o respectivo nome ao objecto guest
                    guest.Username = guestUsername;
                    this.guestList.Add(guest.Username, guest);
                }

                this.desiredSlots = slotList;

            }

            //recebe os slots em que um dado convidado está disponivel e actualiza os slots consensuais
            public void updateConsensualSlots(List<int> availableSlots)
            {
                //lista de slots que deixaram de ser consensuais e por isso são removidos da lista de consensuais
                List<int> slotsToRemove = new List<int>();

                lock (consensualSlots)
                {
                    //se um convidado nao esta disponivel para nenhum slot
                    //a lista dos slots consensuais é "esvaziada"
                    if (availableSlots.Count == 0)
                        consensualSlots.Clear();
                    else
                    {
                        foreach (int slotNumber in consensualSlots)
                        {
                            //todos os slots da lista de consensuais que nao constam da lista de slots para os
                            //quais o cliente está disponível são removidos dos consensuais.
                            //se nao há disponibilidade de um convidado é pq deixou de ser consensual
                            if (!availableSlots.Contains(slotNumber))
                            {
                                slotsToRemove.Add(slotNumber);
                            }
                        }

                        foreach (int slotNumber in slotsToRemove)
                        {
                            consensualSlots.Remove(slotNumber);
                        }
                    }
                }
            }


            public int Id
            {
                get { return id; }
                set { id = value; }
            }

            public String Description
            {
                get { return description; }
                set { description = value; }
            }


            public List<int> ConsensualSlots
            {
                get { return consensualSlots; }
            }

            public int CurrentSlot
            {
                get { return currentSlot; }
                set { currentSlot = value; }
            }

            public Dictionary<String, Guest> GuestList
            {
                get { return guestList; }
            }
        }



        /********************************Guest********************************/

        public class Guest
        {
            private String username;
            private Slot slot = new Slot(); //permite ao anfitriao saber o estado do slot de cada convidado para a reserva

            public String Username
            {
                get { return username; }
                set { username = value; }
            }

            public Slot Slot
            {
                get { return slot; }
                set { slot = value; }
            }
        }



        /********************************Slot********************************/

        public class Slot
        {
            private int number;
            private String state;
            //falta a lista que representa os anfitrioes que pretendem reservar o slot

            public int Number
            {
                get { return number; }
                set { number = value; }
            }

            public String State
            {
                get { return state; }
                set { state = value; }
            }
        }



        /***************************************************************************/
        /**********************ESTRUTURAS DE DADOS PARA GUEST***********************/
        /***************************************************************************/



        /********************************SlotCal********************************/

        //representação dos slots de um calendário
        public class SlotCal
        {
            private int number;
            private String state;
            //quando se escolher fazer book de uma reserva, conterá o seu ID
            private int chosenReservation;
            //permite saber se cada reserva em que se é convidado já mandou fazer BOOK
            private Dictionary<int, ReservationState> reservations = new Dictionary<int, ReservationState>();

            public SlotCal() { }

            public SlotCal(int number, String state, int reservId, ReservationState reservation)
            {
                this.number = number;
                this.state = state;
                this.chosenReservation = 0;
                this.reservations.Add(reservId, reservation);
            }

            public int Number
            {
                get { return number; }
                set { number = value; }
            }

            public String State
            {
                get { return state; }
                set { state = value; }
            }

            public int ChosenReservation
            {
                get { return chosenReservation; }
                set { chosenReservation = value; }
            }

            public Dictionary<int, ReservationState> Reservations
            {
                get { return reservations; }
            }

        }



        /********************************ReservationState********************************/

        public class ReservationState
        {
            private int reservationId;
            private bool booked;

            public ReservationState(int reservationId, bool booked)
            {
                this.reservationId = reservationId;
                this.booked = booked;
            }

            public int ReservationId
            {
                get { return reservationId; }
                set { reservationId = value; }
            }

            public bool Booked
            {
                get { return booked; }
                set { booked = value; }
            }
        }



        public Dictionary<int, Dictionary<String, Thread>> ThreadsToReservate
        {
            get { return threadsToReservate; }
        }

        public Dictionary<int, Dictionary<String, Thread>> ThreadsToBook
        {
            get { return threadsToBook; }
        }
    }
}
